#include <CL/cl.hpp>
#include <iostream>

#ifdef __cplusplus
extern "C" {
#endif

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>

#ifdef __cplusplus
}
#endif

#include "support.h"

#define NFD 10000
#define PROGRAM_FILE "xxx.cl"
#define KERNEL_FUNC "hello_kernel"

template <class T> void
/*RunTest(cl_device_id id, cl_context ctx, cl_command_queue queue, ResultDatabase &resultDB,
        int npasses, int verbose, int quiet, float repeatF, size_t localSize, ProgressBar &pb,
        const char* typeName, const char* precision, const char* pragmaText);
*/
RunTest(cl_device_id id, cl_context ctx, cl_command_queue queue, const char* pragmaText);
// Generate OpenCL kernel code based on benchmark type struct
//void generateKernel (ostringstream &oss, struct _benchmark_type &test, const char* tName, const char* header);

int main (int argc, char *argv[]) {

	int platform = 0;
	int device = 0; 
/*
        cl::Device     id    = ListDevicesAndGetDevice(platform, device);
        std::vector<cl::Device> ctxDevices;
        ctxDevices.push_back( id );
        cl::Context ctx( ctxDevices );
        cl::CommandQueue queue( ctx, id, CL_QUEUE_PROFILING_ENABLE );
*/

	std::vector<cl::Platform> platformList;
	cl::Platform::get(&platformList);

	cl_context_properties cpros[] = {
		CL_CONTEXT_PLATFORM,
		(cl_context_properties)(platformList[0])(),0
	};
	cl::Context context(CL_DEVICE_TYPE_GPU,cpros);

	typedef std::vector<cl::Device> deviceContainer;
	deviceContainer devices = context.getInfo<CL_CONTEXT_DEVICES>();

	cl::CommandQueue queue(context,devices[0],CL_QUEUE_PROFILING_ENABLE);

	cl_device_id devid = devices[0]();
	cl_context ctx = context();
	cl_command_queue cq = queue(); 

	std::string pragma = std::string("#pragma OPENCL EXTENSION cl_amd_fp64: enable");
	RunTest<float>(devid, ctx, cq, pragma.c_str());

return 0;
}

template <class T> void
RunTest(cl_device_id id, cl_context ctx, cl_command_queue queue, const char* pragmaText)
{
	FILE *program_handle;
   	char *program_buffer, *program_log;
	size_t program_size, log_size;

	cl_int err;
	T *hMem1, *hMem2, *hMem3;	
	hMem1 = new T[NFD];
	hMem2 = new T[NFD];
	hMem3 = new T[NFD];

	for (int ff = 0; ff < (NFD/2); ff++){
		hMem1[ff] = hMem1[NFD-1-ff] = (T)ff; 
		hMem2[ff] = hMem2[NFD-1-ff] = (T)ff;
	}
	
	cl_mem mem1;
	mem1 = clCreateBuffer(ctx, CL_MEM_READ_WRITE,
                                 sizeof(T)*NFD, NULL, NULL);
	cl_kernel kernel;
// Issue a copy to force device allocation
       err = clEnqueueWriteBuffer(queue, mem1, true, 0,
                               NFD*sizeof(T), hMem1,
                               0, NULL, NULL);

/*       // Create kernel program object
       const char* progSource[] = {kernelCode.c_str()};
       cl_program prog = clCreateProgramWithSource(ctx, 1, progSource,
           NULL, &err);*/
	   /* Read program file and place content into buffer */
   	program_handle = fopen(PROGRAM_FILE, "r");
   	if(program_handle == NULL) {
      	perror("Couldn't find the program file");
      	exit(1);
   	}
   	fseek(program_handle, 0, SEEK_END);
   	program_size = ftell(program_handle);
   	rewind(program_handle);
   	program_buffer = (char*)calloc(program_size+1, sizeof(char));
   	fread(program_buffer, sizeof(char), program_size, program_handle);
   	fclose(program_handle);	

// segment from cl.hpp example
	cl::Program::Sources source(1,
		std::make_pair(program_buffer,strlen(program_buffer)));

 	cl::Program program_ = cl::Program(ctx, source);
//	program_.build(devices);
 	cl_program prg = program_(); 
/*
	cl::Kernel kernel(program_, "hello", &err);
  
	cl::Event event;
	cl::CommandQueue queue(context, devices[0], 0, &err);
		queue.enqueueNDRangeKernel(
		kernel, 
		cl::NullRange, 
		cl::NDRange(4,4),
		cl::NullRange,
		NULL,
		&event); 

	event.wait();
*/
/* Build program */
	std::cout<< "build program start"<<std::endl;	
	err = clBuildProgram(prg, 0, NULL, "-w", NULL, NULL);
	if(err < 0) {

      /* Find size of log and print to std output */
	clGetProgramBuildInfo(prg, id, CL_PROGRAM_BUILD_LOG,
		0, NULL, &log_size);
	program_log = (char*) calloc(log_size+1, sizeof(char));
	clGetProgramBuildInfo(prg, id, CL_PROGRAM_BUILD_LOG,
		log_size+1, program_log, NULL);
	printf("%s\n", program_log);
	free(program_log);
	exit(1);
	}		
	std::cout<< "build program over"<<std::endl;

/* Create a kernel */
	std::cout <<"create kernel start"<<std::endl;
	kernel = clCreateKernel(prg, KERNEL_FUNC, &err);
	if(err < 0) {
		printf("Couldn't create a kernel: %d", err);
		exit(1);
	};
	std::cout<< "create kernel over "<< err <<std::endl;

/* allocate memory*/
	std::cout<<"allocate mem start"<<std::endl;
	cl_mem a = clCreateBuffer(ctx,
		CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
		NFD*sizeof(T), hMem1, &err);
	cl_mem b = clCreateBuffer(ctx,
		CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
		NFD*sizeof(T), hMem2, &err);
	std::cout<<"allocate mem over "<< err <<std::endl;

/* Create kernel argument */
	std::cout << "create kernel arg start"<<std::endl;
	err = clSetKernelArg(kernel, 0, sizeof(cl_mem), &a);
	std::cout<< "arg1 over "<< err <<std::endl;
	// |= interesting way for checking error
	err |= clSetKernelArg(kernel, 1, sizeof(cl_mem), &b);
	std::cout<< "arg2 over "<< err <<std::endl;
	err |= clSetKernelArg(kernel, 2, sizeof(cl_mem), &mem1);
	std::cout<< "arg3 over "<< err <<std::endl;
	CL_CHECK_ERROR(err);
	if(err != CL_SUCCESS) {
		std::cout << "kernel arg error, "<<"CL_SUCCESS is "<< CL_SUCCESS<<std::endl;
		printf("Couldn't set a kernel argument");
		exit(1);
	};
	std::cout << "create kernel arg over "<< err <<std::endl;

/* Enqueue kernel */
	std::cout << "enqueue kernel start"<<std::endl;
	size_t global_size = NFD;
	size_t local_size = 1;
	err = clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &global_size,
		&local_size, 0, NULL, NULL);
	if(err < 0) {
		perror("Couldn't enqueue the kernel");
		printf("Error: %d\n", err);
		exit(1);	
	}
	std::cout << "enqueue kernel over"<<std::endl;

/* Read the results */
	std::cout << "enqueue read buffer start"<<std::endl;
	err = clEnqueueReadBuffer(queue, mem1, CL_TRUE, 0,
		NFD*sizeof(T), hMem3, 0, NULL, NULL);
	if(err < 0) {
		perror("Couldn't read the buffer");
		exit(1);
	}
	std::cout << "enqueue read buffer over"<<std::endl;

/* Print the result */
	for (int kk = 0; kk<10; kk++){
		std::cout<< hMem1[kk] << ","<<hMem2[kk]<<","<<hMem3[kk]<<std::endl;
	}

	clReleaseMemObject(a);
	clReleaseMemObject(b);
	clReleaseMemObject(mem1);

}
